﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.templates;
using l1cs.server.model.Instance;
using l1cs.server.serverpackets;
using l1cs.server.model.skill;
using l1cs.server.datatables;
using l1cs.server.server.types;
using l1cs.server.model.map;
using l1cs.server.utils;
using System.Threading;
using System.Collections;

namespace l1cs.server.model.Instance
{
    public class L1NpcInstance : L1Character
    {
        public const int MOVE_SPEED = 0;
        public const int ATTACK_SPEED = 1;
        public const int MAGIC_SPEED = 2;

        public const int HIDDEN_STATUS_NONE = 0;
        public const int HIDDEN_STATUS_SINK = 1;
        public const int HIDDEN_STATUS_FLY = 2;
        public const int HIDDEN_STATUS_ICE = 3;

        /// <summary>チャットタイミングは出現時</summary>
        public const int CHAT_TIMING_APPEARANCE = 0;
        /// <summary>チャットタイミングは死亡時</summary>
        public const int CHAT_TIMING_DEAD = 1;
        /// <summary>チャットタイミングは空から降りてきた時</summary>
        public const int CHAT_TIMING_HIDE = 2;
        /// <summary>チャットタイミングはゲームタイム？</summary>
        public const int CHAT_TIMING_GAME_TIME = 3;

        private static Logger _log = Logger.getLogger( typeof( L1NpcInstance ).FullName );
        private L1Npc _npcTemplate;

        private L1Spawn _spawn;
        private int _spawnNumber; // L1Spawnで管理されているナンバー

        private int _petcost; // ペットになったときのコスト
        public L1Inventory _inventory = new L1Inventory();
        private L1MobSkillUse mobSkill;
        private static Random _random = new Random();

        // 対象を初めて発見したとき。（テレポート用）
        private bool firstFound = true;

        // 経路探索範囲（半径） ※上げすぎ注意！！
        public static int courceRange = 15;

        // 吸われたMP
        private int _drainedMana = 0;

        // 休憩
        private bool _rest = false;

        // ランダム移動時の距離と方向
        private int _randomMoveDistance = 0;

        private int _randomMoveDirection = 0;

        L1NpcInstance() { }
        // ■■■■■■■■■■■■■ ＡＩ関連 ■■■■■■■■■■■

        //interface NpcAI
        //{
        //    public void start();
        //}

        protected void startAI()
        {
            if ( Config.NPCAI_IMPLTYPE == 1 )
            {
                //new NpcAITimerImpl().start();
                NpcAITimerStart();
            }
            else if ( Config.NPCAI_IMPLTYPE == 2 )
            {
                //new NpcAIThreadImpl().start();
                NpcAIThreadStart();
            }
            else
            {
                //new NpcAITimerImpl().start();
                NpcAITimerStart();
            }
        }

        /**
         * マルチ(コア)プロセッサをサポートする為のタイマープール。 AIの実装タイプがタイマーの場合に使用される。
         */
        //private const TimerPool _timerPool = new TimerPool( 4 );

        //class NpcAITimerImpl : L1NpcInstance
        //{
        /**
         * 死亡処理の終了を待つタイマー
         */
        //private class DeathSyncTimer : NpcAITimerImpl
        //{
        //private void schedule( int delay )
        //{
        //    _timerPool.getTimer().schedule(new DeathSyncTimer(), delay);
        //}

        public void DeathSyncTimerSub( object state )
        {
            if ( isDeathProcessing() )
            {
                //schedule( getSleepTime() );
                new Timer( new TimerCallback( DeathSyncTimerSub ), null, getSleepTime(), Timeout.Infinite );
                return;
            }
            allTargetClear();
            setAiRunning( false );
        }
        //}

        public void NpcAITimerStart()
        {
            setAiRunning( true );
            //_timerPool.getTimer().schedule(NpcAITimerImpl.this, 0);
            new Timer( new TimerCallback( NpcAITimerSub ), null, 0, Timeout.Infinite );

        }

        private void NpcAITimerImplStop()
        {
            mobSkill.resetAllSkillUseCount();
            //_timerPool.getTimer().schedule(new DeathSyncTimer(), 0); // 死亡同期を開始
            new Timer( new TimerCallback( DeathSyncTimerSub ), null, 0, Timeout.Infinite );
        }

        // 同じインスタンスをTimerへ登録できない為、苦肉の策。
        private void schedule( int delay )
        {
            //_timerPool.getTimer().schedule( new NpcAITimerImpl(), delay );
        }

        public void NpcAITimerSub( object state )
        {
            if ( _destroyed || isDead() || getCurrentHp() <= 0
                    || getHiddenStatus() != HIDDEN_STATUS_NONE )
            {
                NpcAITimerImplStop();
                return;
            }

            // XXX 同期がとても怪しげな麻痺判定
            if ( 0 < _paralysisTime )
            {
                schedule( _paralysisTime );
                _paralysisTime = 0;
                setParalyzed( false );
                return;
            }
            else if ( isParalyzed() || isSleeped() )
            {
                schedule( 200 );
                return;
            }

            if ( !AIProcess() )
            { // AIを続けるべきであれば、次の実行をスケジュールし、終了
                schedule( getSleepTime() );
                return;
            }
            NpcAITimerImplStop();
        }
        //}

        //class NpcAIThreadImpl : L1NpcInstance
        //{
        public void NpcAIThreadStart()
        {
            //GeneralThreadPool.getInstance().execute(NpcAIThreadImpl.this);
            ThreadPool.QueueUserWorkItem( new WaitCallback( NpcAIThread ) );
        }

        public void NpcAIThread( object state )
        {
            setAiRunning( true );
            while ( !_destroyed && !isDead() && getCurrentHp() > 0
                    && getHiddenStatus() == HIDDEN_STATUS_NONE )
            {
                /*
                 * if (_paralysisTime > 0) { try {
                 * Thread.sleep(_paralysisTime); } catch (Exception
                 * exception) { break; } finally { setParalyzed(false);
                 * _paralysisTime = 0; } }
                 */
                while ( isParalyzed() || isSleeped() )
                {
                    Thread.Sleep( 200 );
                }

                if ( AIProcess() )
                {
                    break;
                }
                // 指定時間分スレッド停止
                Thread.Sleep( getSleepTime() );
            }
            mobSkill.resetAllSkillUseCount();
            do
            {
                Thread.Sleep( getSleepTime() );
            } while ( isDeathProcessing() );
            allTargetClear();
            setAiRunning( false );
        }
        //}

        // ＡＩの処理 (返り値はＡＩ処理を終了するかどうか)
        private bool AIProcess()
        {
            setSleepTime( 300 );

            checkTarget();
            if ( _target == null && _master == null )
            {
                // 空っぽの場合はターゲットを探してみる
                // （主人がいる場合は自分でターゲットを探さない）
                searchTarget();
            }

            onItemUse();

            if ( _target == null )
            {
                // ターゲットがいない場合
                checkTargetItem();
                if ( isPickupItem() && _targetItem == null )
                {
                    // アイテム拾う子の場合はアイテムを探してみる
                    searchTargetItem();
                }

                if ( _targetItem == null )
                {
                    if ( noTarget() )
                    {
                        return true;
                    }
                }
                else
                {
                    // onTargetItem();
                    L1Inventory groundInventory = L1World.getInstance()
                            .getInventory( _targetItem.getX(), _targetItem.getY(),
                                    _targetItem.getMapId() );
                    if ( groundInventory.checkItem( _targetItem.getItemId() ) )
                    {
                        onTargetItem();
                    }
                    else
                    {
                        _targetItemList.Remove( _targetItem );
                        _targetItem = null;
                        setSleepTime( 1000 );
                        return false;
                    }
                }
            }
            else
            { // ターゲットがいる場合
                if ( getHiddenStatus() == HIDDEN_STATUS_NONE )
                {
                    onTarget();
                }
                else
                {
                    return true;
                }
            }

            return false; // ＡＩ処理続行
        }

        // アイテム使用処理（Ｔｙｐｅによって結構違うのでオーバライドで実装）
        virtual public void onItemUse() { }

        // ターゲットを探す（Ｔｙｐｅによって結構違うのでオーバライドで実装）
        virtual public void searchTarget() { }

        // 有効なターゲットか確認及び次のターゲットを設定
        public void checkTarget()
        {
            if ( _target == null
                    || _target.getMapId() != getMapId()
                    || _target.getCurrentHp() <= 0
                    || _target.isDead()
                    || ( _target.isInvisble() && !getNpcTemplate().is_agrocoi() && !_hateList
                            .containsKey( _target ) ) )
            {
                if ( _target != null )
                {
                    tagertClear();
                }
                if ( !_hateList.isEmpty() )
                {
                    _target = _hateList.getMaxHateCharacter();
                    checkTarget();
                }
            }
        }

        // ヘイトの設定
        public void setHate( L1Character cha, int hate )
        {
            if ( cha != null && cha.getId() != getId() )
            {
                if ( !isFirstAttack() && hate != 0 )
                {
                    // hate += 20; // ＦＡヘイト
                    hate += getMaxHp() / 10; // ＦＡヘイト
                    setFirstAttack( true );
                }

                _hateList.add( cha, hate );
                _dropHateList.add( cha, hate );
                _target = _hateList.getMaxHateCharacter();
                checkTarget();
            }
        }

        // リンクの設定
        virtual public void setLink( L1Character cha ) { }

        // 仲間意識によりアクティブになるＮＰＣの検索（攻撃者がプレイヤーのみ有効）
        public void serchLink( L1PcInstance targetPlayer, int family )
        {
            //ArrayList targetKnownObjects = targetPlayer.getKnownObjects();
            foreach ( Object knownObject in targetPlayer.getKnownObjects() )
            {
                if ( knownObject is L1NpcInstance )
                {
                    L1NpcInstance npc = (L1NpcInstance)knownObject;
                    if ( npc.getNpcTemplate().get_agrofamily() > 0 )
                    {
                        // 仲間に対してアクティブになる場合
                        if ( npc.getNpcTemplate().get_agrofamily() == 1 )
                        {
                            // 同種族に対してのみ仲間意識
                            if ( npc.getNpcTemplate().get_family() == family )
                            {
                                npc.setLink( targetPlayer );
                            }
                        }
                        else
                        {
                            // 全てのＮＰＣに対して仲間意識
                            npc.setLink( targetPlayer );
                        }
                    }
                    L1MobGroupInfo mobGroupInfo = getMobGroupInfo();
                    if ( mobGroupInfo != null )
                    {
                        if ( getMobGroupId() != 0
                                && getMobGroupId() == npc.getMobGroupId() )
                        { // 同じグループ
                            npc.setLink( targetPlayer );
                        }
                    }
                }
            }
        }

        // ターゲットがいる場合の処理
        public void onTarget()
        {
            setActived( true );
            _targetItemList.Clear();
            _targetItem = null;
            L1Character target = _target; // ここから先は_targetが変わると影響出るので別領域に参照確保
            if ( getAtkspeed() == 0 )
            { // 逃げるキャラ
                if ( getPassispeed() > 0 )
                { // 移動できるキャラ
                    int escapeDistance = 15;
                    if ( hasSkillEffect( 40 ) == true )
                    {
                        escapeDistance = 1;
                    }
                    if ( getLocation().getTileLineDistance( target.getLocation() ) > escapeDistance )
                    { // ターゲットから逃げるの終了
                        tagertClear();
                    }
                    else
                    { // ターゲットから逃げる
                        int dir = targetReverseDirection( target.getX(), target
                                .getY() );
                        dir = checkObject( getX(), getY(), getMapId(), dir );
                        setDirectionMove( dir );
                        setSleepTime( calcSleepTime( getPassispeed(), MOVE_SPEED ) );
                    }
                }
            }
            else
            { // 逃げないキャラ
                if ( isAttackPosition( target.getX(), target.getY(), getNpcTemplate()
                        .get_ranged() ) )
                { // 攻撃可能位置
                    if ( mobSkill.isSkillTrigger( target ) )
                    { // トリガの条件に合うスキルがある
                        if ( _random.Next( 2 ) >= 1 )
                        { // 一定の確率で物理攻撃
                            setHeading( targetDirection( target.getX(), target
                                    .getY() ) );
                            attackTarget( target );
                        }
                        else
                        {
                            if ( mobSkill.skillUse( target, true ) )
                            { // スキル使用(mobskill.sqlのTriRndに従う)
                                setSleepTime( calcSleepTime( mobSkill.getSleepTime(),
                                        MAGIC_SPEED ) );
                            }
                            else
                            { // スキル使用が失敗したら物理攻撃
                                setHeading( targetDirection( target.getX(), target
                                        .getY() ) );
                                attackTarget( target );
                            }
                        }
                    }
                    else
                    {
                        setHeading( targetDirection( target.getX(), target.getY() ) );
                        attackTarget( target );
                    }
                }
                else
                { // 攻撃不可能位置
                    if ( mobSkill.skillUse( target, false ) )
                    { // スキル使用(mobskill.sqlのTriRndに従わず、発動確率は100%。ただしサモン、強制変身は常にTriRndに従う。)
                        setSleepTime( calcSleepTime( mobSkill.getSleepTime(),
                                MAGIC_SPEED ) );
                        return;
                    }

                    if ( getPassispeed() > 0 )
                    {
                        // 移動できるキャラ
                        int distance = getLocation().getTileDistance(
                                target.getLocation() );
                        if ( firstFound == true && getNpcTemplate().is_teleport()
                                && distance > 3 && distance < 15 )
                        {
                            if ( nearTeleport( target.getX(), target.getY() ) == true )
                            {
                                firstFound = false;
                                return;
                            }
                        }

                        if ( getNpcTemplate().is_teleport()
                                && 20 > _random.Next( 100 )
                                && getCurrentMp() >= 10 && distance > 6
                                && distance < 15 )
                        { // テレポート移動
                            if ( nearTeleport( target.getX(), target.getY() ) == true )
                            {
                                return;
                            }
                        }
                        int dir = moveDirection( target.getX(), target.getY() );
                        if ( dir == -1 )
                        {
                            tagertClear();
                        }
                        else
                        {
                            setDirectionMove( dir );
                            setSleepTime( calcSleepTime( getPassispeed(),
                                    MOVE_SPEED ) );
                        }
                    }
                    else
                    {
                        // 移動できないキャラ（ターゲットから排除、ＰＴのときドロップチャンスがリセットされるけどまぁ自業自得）
                        tagertClear();
                    }
                }
            }
        }

        // 目標を指定のスキルで攻撃
        public void attackTarget( L1Character target )
        {
            if ( target is L1PcInstance )
            {
                L1PcInstance player = (L1PcInstance)target;
                if ( player.isTeleport() )
                { // テレポート処理中
                    return;
                }
            }
            else if ( target is L1PetInstance )
            {
                L1PetInstance pet = (L1PetInstance)target;
                L1Character cha = pet.getMaster();
                if ( cha is L1PcInstance )
                {
                    L1PcInstance player = (L1PcInstance)cha;
                    if ( player.isTeleport() )
                    { // テレポート処理中
                        return;
                    }
                }
            }
            else if ( target is L1SummonInstance )
            {
                L1SummonInstance summon = (L1SummonInstance)target;
                L1Character cha = summon.getMaster();
                if ( cha is L1PcInstance )
                {
                    L1PcInstance player = (L1PcInstance)cha;
                    if ( player.isTeleport() )
                    { // テレポート処理中
                        return;
                    }
                }
            }
            if ( this is L1PetInstance )
            {
                L1PetInstance pet = (L1PetInstance)this;
                L1Character cha = pet.getMaster();
                if ( cha is L1PcInstance )
                {
                    L1PcInstance player = (L1PcInstance)cha;
                    if ( player.isTeleport() )
                    { // テレポート処理中
                        return;
                    }
                }
            }
            else if ( this is L1SummonInstance )
            {
                L1SummonInstance summon = (L1SummonInstance)this;
                L1Character cha = summon.getMaster();
                if ( cha is L1PcInstance )
                {
                    L1PcInstance player = (L1PcInstance)cha;
                    if ( player.isTeleport() )
                    { // テレポート処理中
                        return;
                    }
                }
            }

            if ( target is L1NpcInstance )
            {
                L1NpcInstance npc = (L1NpcInstance)target;
                if ( npc.getHiddenStatus() != HIDDEN_STATUS_NONE )
                { // 地中に潜っているか、飛んでいる
                    allTargetClear();
                    return;
                }
            }

            bool isCounterBarrier = false;
            L1Attack attack = new L1Attack( this, target );
            if ( attack.calcHit() )
            {
                if ( target.hasSkillEffect( L1SkillId.COUNTER_BARRIER ) )
                {
                    L1Magic magic = new L1Magic( target, this );
                    bool isProbability = magic
                            .calcProbabilityMagic( L1SkillId.COUNTER_BARRIER );
                    bool isShortDistance = attack.isShortDistance();
                    if ( isProbability && isShortDistance )
                    {
                        isCounterBarrier = true;
                    }
                }
                if ( !isCounterBarrier )
                {
                    attack.calcDamage();
                }
            }
            if ( isCounterBarrier )
            {
                attack.actionCounterBarrier();
                attack.commitCounterBarrier();
            }
            else
            {
                attack.action();
                attack.commit();
            }
            setSleepTime( calcSleepTime( getAtkspeed(), ATTACK_SPEED ) );
        }

        // ターゲットアイテムを探す
        public void searchTargetItem()
        {
            List<L1GroundInventory> gInventorys =
                    new List<L1GroundInventory>();

            foreach ( L1Object obj in L1World.getInstance().getVisibleObjects( this ) )
            {
                if ( obj != null && obj is L1GroundInventory )
                {
                    gInventorys.Add( (L1GroundInventory)obj );
                }
            }
            if ( gInventorys.Count == 0 )
            {
                return;
            }

            // 拾うアイテム(のインベントリ)をランダムで選定
            int pickupIndex = (int)( new Random().Next( 1 ) * gInventorys.Count );
            L1GroundInventory inventory = gInventorys[pickupIndex];
            foreach ( L1ItemInstance item in inventory.getItems() )
            {
                if ( getInventory().checkAddItem( item, item.getCount() )
                        == L1Inventory.OK )
                { // 持てるならターゲットアイテムに加える
                    _targetItem = item;
                    _targetItemList.Add( _targetItem );
                }
            }
        }

        // 飛んでいる状態からアイテムを探し、あれば降りて拾う
        public void searchItemFromAir()
        {
            List<L1GroundInventory> gInventorys = new List<L1GroundInventory>();

            foreach ( L1Object obj in L1World.getInstance().getVisibleObjects( this ) )
            {
                if ( obj != null && obj is L1GroundInventory )
                {
                    gInventorys.Add( (L1GroundInventory)obj );
                }
            }
            if ( gInventorys.Count == 0 )
            {
                return;
            }

            // 拾うアイテム(のインベントリ)をランダムで選定
            int pickupIndex = (int)( ( new Random() ).Next( 1 ) * gInventorys.Count );
            L1GroundInventory inventory = gInventorys[pickupIndex];
            foreach ( L1ItemInstance item in inventory.getItems() )
            {
                if ( item.getItem().getType() == 6 // potion
                        || item.getItem().getType() == 7 )
                { // food
                    if ( getInventory().checkAddItem( item, item.getCount() )
                            == L1Inventory.OK )
                    {
                        if ( getHiddenStatus() == HIDDEN_STATUS_FLY )
                        {
                            setHiddenStatus( HIDDEN_STATUS_NONE );
                            broadcastPacket( new S_DoActionGFX( getId(), ActionCodes.ACTION_Movedown ) );
                            setStatus( 0 );
                            broadcastPacket( new S_NPCPack( this ) );
                            onNpcAI();
                            startChat( CHAT_TIMING_HIDE );
                            _targetItem = item;
                            _targetItemList.Add( _targetItem );
                        }
                    }
                }
            }
        }

        public static void shuffle( L1Object[] arr )
        {
            for ( int i = arr.Length - 1; i > 0; i-- )
            {
                int t = (int)( new Random().Next( 1 ) * i );

                // 選ばれた値と交換する
                L1Object tmp = arr[i];
                arr[i] = arr[t];
                arr[t] = tmp;
            }
        }

        // 有効なターゲットアイテムか確認及び次のターゲットアイテムを設定
        public void checkTargetItem()
        {
            if ( _targetItem == null
                    || _targetItem.getMapId() != getMapId()
                    || getLocation().getTileDistance( _targetItem.getLocation() ) > 15 )
            {
                if ( _targetItemList.Count != 0 )
                {
                    _targetItem = _targetItemList[0];
                    _targetItemList.Remove( _targetItem );
                    checkTargetItem();
                }
                else
                {
                    _targetItem = null;
                }
            }
        }

        // ターゲットアイテムがある場合の処理
        public void onTargetItem()
        {
            if ( getLocation().getTileLineDistance( _targetItem.getLocation() ) == 0 )
            { // ピックアップ可能位置
                pickupTargetItem( _targetItem );
            }
            else
            { // ピックアップ不可能位置
                int dir = moveDirection( _targetItem.getX(), _targetItem.getY() );
                if ( dir == -1 )
                { // 拾うの諦め
                    _targetItemList.Remove( _targetItem );
                    _targetItem = null;
                }
                else
                { // ターゲットアイテムへ移動
                    setDirectionMove( dir );
                    setSleepTime( calcSleepTime( getPassispeed(), MOVE_SPEED ) );
                }
            }
        }

        // アイテムを拾う
        public void pickupTargetItem( L1ItemInstance targetItem )
        {
            L1Inventory groundInventory = L1World.getInstance().getInventory(
                    targetItem.getX(), targetItem.getY(), targetItem.getMapId() );
            L1ItemInstance item = groundInventory.tradeItem( targetItem, targetItem
                    .getCount(), getInventory() );
            turnOnOffLight();
            onGetItem( item );
            _targetItemList.Remove( _targetItem );
            _targetItem = null;
            setSleepTime( 1000 );
        }

        // ターゲットがいない場合の処理 (返り値はＡＩ処理を終了するかどうか)
        virtual
        public bool noTarget()
        {
            if ( _master != null && _master.getMapId() == getMapId()
                    && getLocation().getTileLineDistance( _master
                            .getLocation() ) > 2 )
            { // 主人が同じマップにいて離れてる場合は追尾
                int dir = moveDirection( _master.getX(), _master.getY() );
                if ( dir != -1 )
                {
                    setDirectionMove( dir );
                    setSleepTime( calcSleepTime( getPassispeed(), MOVE_SPEED ) );
                }
                else
                {
                    return true;
                }
            }
            else
            {
                if ( L1World.getInstance().getRecognizePlayer( this ).Count == 0 )
                {
                    return true; // 周りにプレイヤーがいなくなったらＡＩ処理終了
                }
                // 移動できるキャラはランダムに動いておく
                if ( _master == null && getPassispeed() > 0 && !isRest() )
                {
                    // グループに属していないorグループに属していてリーダーの場合、ランダムに動いておく
                    L1MobGroupInfo mobGroupInfo = getMobGroupInfo();
                    if ( mobGroupInfo == null
                            || mobGroupInfo != null && mobGroupInfo
                                    .isLeader( this ) )
                    {
                        // 移動する予定の距離を移動し終えたら、新たに距離と方向を決める
                        // そうでないなら、移動する予定の距離をデクリメント
                        if ( _randomMoveDistance == 0 )
                        {
                            _randomMoveDistance = _random.Next( 5 ) + 1;
                            _randomMoveDirection = _random.Next( 20 );
                            // ホームポイントから離れすぎないように、一定の確率でホームポイントの方向に補正
                            if ( getHomeX() != 0 && getHomeY() != 0
                                    && _randomMoveDirection < 8
                                    && _random.Next( 3 ) == 0 )
                            {
                                _randomMoveDirection = moveDirection( getHomeX(),
                                        getHomeY() );
                            }
                        }
                        else
                        {
                            _randomMoveDistance--;
                        }
                        int dir = checkObject( getX(), getY(), getMapId(),
                                _randomMoveDirection );
                        if ( dir != -1 )
                        {
                            setDirectionMove( dir );
                            setSleepTime( calcSleepTime( getPassispeed(),
                                    MOVE_SPEED ) );
                        }
                    }
                    else
                    { // リーダーを追尾
                        L1NpcInstance leader = mobGroupInfo.getLeader();
                        if ( getLocation().getTileLineDistance( leader
                                .getLocation() ) > 2 )
                        {
                            int dir = moveDirection( leader.getX(), leader.getY() );
                            if ( dir == -1 )
                            {
                                return true;
                            }
                            else
                            {
                                setDirectionMove( dir );
                                setSleepTime( calcSleepTime( getPassispeed(),
                                        MOVE_SPEED ) );
                            }
                        }
                    }
                }
            }
            return false;
        }

        virtual
        public void onFinalAction( L1PcInstance pc, String s )
        {
        }

        // 現在のターゲットを削除
        public void tagertClear()
        {
            if ( _target == null )
            {
                return;
            }
            _hateList.remove( _target );
            _target = null;
        }

        // 指定されたターゲットを削除
        public void targetRemove( L1Character target )
        {
            _hateList.remove( target );
            if ( _target != null && _target.Equals( target ) )
            {
                _target = null;
            }
        }

        // 全てのターゲットを削除
        public void allTargetClear()
        {
            _hateList.clear();
            _dropHateList.clear();
            _target = null;
            _targetItemList.Clear();
            _targetItem = null;
        }

        // マスターの設定
        public void setMaster( L1Character cha )
        {
            _master = cha;
        }

        // マスターの取得
        public L1Character getMaster()
        {
            return _master;
        }

        // ＡＩトリガ
        virtual
        public void onNpcAI()
        {
        }

        // アイテム精製
        public void refineItem()
        {

            int[] materials = null;
            int[] counts = null;
            int[] createitem = null;
            int[] createcount = null;

            if ( _npcTemplate.get_npcId() == 45032 )
            { // ブロッブ
                // オリハルコンソードの刀身
                if ( getExp() != 0 && !_inventory.checkItem( 20 ) )
                {
                    materials = new int[] { 40508, 40521, 40045 };
                    counts = new int[] { 150, 3, 3 };
                    createitem = new int[] { 20 };
                    createcount = new int[] { 1 };
                    if ( _inventory.checkItem( materials, counts ) )
                    {
                        for ( int i = 0; i < materials.Length; i++ )
                        {
                            _inventory.consumeItem( materials[i], counts[i] );
                        }
                        for ( int j = 0; j < createitem.Length; j++ )
                        {
                            _inventory.storeItem( createitem[j], createcount[j] );
                        }
                    }
                }
                // ロングソードの刀身
                if ( getExp() != 0 && !_inventory.checkItem( 19 ) )
                {
                    materials = new int[] { 40494, 40521 };
                    counts = new int[] { 150, 3 };
                    createitem = new int[] { 19 };
                    createcount = new int[] { 1 };
                    if ( _inventory.checkItem( materials, counts ) )
                    {
                        for ( int i = 0; i < materials.Length; i++ )
                        {
                            _inventory.consumeItem( materials[i], counts[i] );
                        }
                        for ( int j = 0; j < createitem.Length; j++ )
                        {
                            _inventory.storeItem( createitem[j], createcount[j] );
                        }
                    }
                }
                // ショートソードの刀身
                if ( getExp() != 0 && !_inventory.checkItem( 3 ) )
                {
                    materials = new int[] { 40494, 40521 };
                    counts = new int[] { 50, 1 };
                    createitem = new int[] { 3 };
                    createcount = new int[] { 1 };
                    if ( _inventory.checkItem( materials, counts ) )
                    {
                        for ( int i = 0; i < materials.Length; i++ )
                        {
                            _inventory.consumeItem( materials[i], counts[i] );
                        }
                        for ( int j = 0; j < createitem.Length; j++ )
                        {
                            _inventory.storeItem( createitem[j], createcount[j] );
                        }
                    }
                }
                // オリハルコンホーン
                if ( getExp() != 0 && !_inventory.checkItem( 100 ) )
                {
                    materials = new int[] { 88, 40508, 40045 };
                    counts = new int[] { 4, 80, 3 };
                    createitem = new int[] { 100 };
                    createcount = new int[] { 1 };
                    if ( _inventory.checkItem( materials, counts ) )
                    {
                        for ( int i = 0; i < materials.Length; i++ )
                        {
                            _inventory.consumeItem( materials[i], counts[i] );
                        }
                        for ( int j = 0; j < createitem.Length; j++ )
                        {
                            _inventory.storeItem( createitem[j], createcount[j] );
                        }
                    }
                }
                // ミスリルホーン
                if ( getExp() != 0 && !_inventory.checkItem( 89 ) )
                {
                    materials = new int[] { 88, 40494 };
                    counts = new int[] { 2, 80 };
                    createitem = new int[] { 89 };
                    createcount = new int[] { 1 };
                    if ( _inventory.checkItem( materials, counts ) )
                    {
                        for ( int i = 0; i < materials.Length; i++ )
                        {
                            _inventory.consumeItem( materials[i], counts[i] );
                        }
                        for ( int j = 0; j < createitem.Length; j++ )
                        {
                            L1ItemInstance item = _inventory.storeItem( createitem[j], createcount[j] );
                            if ( getNpcTemplate().get_digestitem() > 0 )
                            {
                                setDigestItem( item );
                            }
                        }
                    }
                }
            }
            else if ( _npcTemplate.get_npcId() == 81069 )
            { // ドッペルゲンガー（クエスト）
                // ドッペルゲンガーの体液
                if ( getExp() != 0 && !_inventory.checkItem( 40542 ) )
                {
                    materials = new int[] { 40032 };
                    counts = new int[] { 1 };
                    createitem = new int[] { 40542 };
                    createcount = new int[] { 1 };
                    if ( _inventory.checkItem( materials, counts ) )
                    {
                        for ( int i = 0; i < materials.Length; i++ )
                        {
                            _inventory.consumeItem( materials[i], counts[i] );
                        }
                        for ( int j = 0; j < createitem.Length; j++ )
                        {
                            _inventory.storeItem( createitem[j], createcount[j] );
                        }
                    }
                }
            }
            else if ( _npcTemplate.get_npcId() == 45166 // ジャックオーランタン
                  || _npcTemplate.get_npcId() == 45167 )
            {
                // パンプキンの種
                if ( getExp() != 0 && !_inventory.checkItem( 40726 ) )
                {
                    materials = new int[] { 40725 };
                    counts = new int[] { 1 };
                    createitem = new int[] { 40726 };
                    createcount = new int[] { 1 };
                    if ( _inventory.checkItem( materials, counts ) )
                    {
                        for ( int i = 0; i < materials.Length; i++ )
                        {
                            _inventory.consumeItem( materials[i], counts[i] );
                        }
                        for ( int j = 0; j < createitem.Length; j++ )
                        {
                            _inventory.storeItem( createitem[j], createcount[j] );
                        }
                    }
                }
            }
        }

        private bool _aiRunning = false; // ＡＩが実行中か
        // ※ＡＩをスタートさせる時にすでに実行されてないか確認する時に使用
        private bool _actived = false; // ＮＰＣがアクティブか
        // ※この値がfalseで_targetがいる場合、アクティブになって初行動とみなしヘイストポーション等を使わせる判定で使用
        private bool _firstAttack = false; // ファーストアッタクされたか
        private int _sleep_time; // ＡＩを停止する時間(ms) ※行動を起こした場合に所要する時間をセット
        protected L1HateList _hateList = new L1HateList();
        protected L1HateList _dropHateList = new L1HateList();
        // ※攻撃するターゲットの判定とＰＴ時のドロップ判定で使用
        protected List<L1ItemInstance> _targetItemList = new List<L1ItemInstance>(); // ダーゲットアイテム一覧
        protected L1Character _target = null; // 現在のターゲット
        protected L1ItemInstance _targetItem = null; // 現在のターゲットアイテム
        protected L1Character _master = null; // 主人orグループリーダー
        private bool _deathProcessing = false; // 死亡処理中か
        // EXP、Drop分配中はターゲットリスト、ヘイトリストをクリアしない

        private int _paralysisTime = 0; // Paralysis RestTime

        public void setParalysisTime( int ptime )
        {
            _paralysisTime = ptime;
        }

        public L1HateList getHateList()
        {
            return _hateList;
        }

        public int getParalysisTime()
        {
            return _paralysisTime;
        }

        // HP自然回復
        public void startHpRegeneration()
        {
            int hprInterval = getNpcTemplate().get_hprinterval();
            int hpr = getNpcTemplate().get_hpr();
            if ( !_hprRunning && hprInterval > 0 && hpr > 0 )
            {
                //_hprTimer = new HprTimer( hpr );
                //L1NpcRegenerationTimer.getInstance().scheduleAtFixedRate(_hprTimer, hprInterval, hprInterval);
                setHprPoing( hpr );
                _hprTimer = new Timer( new TimerCallback( HprTimerSub ), null, hprInterval, hprInterval );
                _hprRunning = true;
            }
        }

        public void stopHpRegeneration()
        {
            if ( _hprRunning )
            {
                _hprTimer.Dispose();
                _hprRunning = false;
            }
        }

        // MP自然回復
        public void startMpRegeneration()
        {
            int mprInterval = getNpcTemplate().get_mprinterval();
            int mpr = getNpcTemplate().get_mpr();
            if ( !_mprRunning && mprInterval > 0 && mpr > 0 )
            {
                //_mprTimer = new MprTimer( mpr );
                //TOTO:L1NpcRegenerationTimer.getInstance().scheduleAtFixedRate(_mprTimer, mprInterval, mprInterval);
                setMprPoint( mpr );
                _mprTimer = new Timer( new TimerCallback( MprTimerSub ), null, mprInterval, mprInterval );
                _mprRunning = true;
            }
        }

        public void stopMpRegeneration()
        {
            if ( _mprRunning )
            {
                _mprTimer.Dispose();
                _mprRunning = false;
            }
        }

        //    // ■■■■■■■■■■■■ タイマー関連 ■■■■■■■■■■

        // ＨＰ自然回復
        private bool _hprRunning = false;

        private Timer _hprTimer;
        //class HprTimer : L1NpcInstance
        //{
        public void HprTimerSub( object state )
        {
            if ( ( !_destroyed && !isDead() )
                    && ( getCurrentHp() > 0 && getCurrentHp() < getMaxHp() ) )
            {
                setCurrentHp( getCurrentHp() + _hprPoint );
            }
            else
            {
                _hprTimer.Dispose();
                _hprRunning = false;
            }
        }

        public void setHprPoing( int point )
        {
            if ( point < 1 )
            {
                point = 1;
            }
            _hprPoint = point;
        }

        private int _hprPoint;
        //}

        // ＭＰ自然回復
        private bool _mprRunning = false;

        private Timer _mprTimer;
        //class MprTimer : L1NpcInstance
        //{
        public void MprTimerSub( object state )
        {
            if ( ( !_destroyed && !isDead() )
                    && ( getCurrentHp() > 0 && getCurrentMp() < getMaxMp() ) )
            {
                setCurrentMp( getCurrentMp() + _mprPoint );
            }
            else
            {
                _mprTimer.Dispose();
                _mprRunning = false;
            }
        }

        public void setMprPoint( int point )
        {
            if ( point < 1 )
            {
                point = 1;
            }
            _mprPoint = point;
        }

        private int _mprPoint;
        //}

        // アイテム消化
        private Dictionary<int, int> _digestItems;
        public bool _digestItemRunning = false;

        //class DigestItemTimer : L1NpcInstance
        //{
        public void DigestItemTimerSub( object state )
        {
            _digestItemRunning = true;
            while ( !_destroyed && _digestItems.Count > 0 )
            {
                Thread.Sleep( 1000 );

                //Object[] keys = _digestItems.keySet().toArray();
                //for (int i = 0; i < keys.length; i++) {
                foreach ( int key in _digestItems.Keys )
                {
                    //Integer key = (Integer) keys[i];
                    int digestCounter = _digestItems[key];
                    digestCounter -= 1;
                    if ( digestCounter <= 0 )
                    {
                        _digestItems.Remove( key );
                        L1ItemInstance digestItem = getInventory().getItem( key );
                        if ( digestItem != null )
                        {
                            getInventory().removeItem( digestItem,
                                    digestItem.getCount() );
                        }
                    }
                    else
                    {
                        _digestItems.Add( key, digestCounter );
                    }
                }
            }
            _digestItemRunning = false;
        }
        //}

        // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

        public L1NpcInstance( L1Npc template )
        {
            setStatus( 0 );
            setMoveSpeed( 0 );
            setDead( false );
            setStatus( 0 );
            setreSpawn( false );

            if ( template != null )
            {
                setting_template( template );
            }
        }

        // 指定のテンプレートで各種値を初期化
        public void setting_template( L1Npc template )
        {
            _npcTemplate = template;
            int randomlevel = 0;
            double rate = 0;
            double diff = 0;
            setName( template.get_name() );
            setNameId( template.get_nameid() );
            if ( template.get_randomlevel() == 0 )
            { // ランダムLv指定なし
                setLevel( template.get_level() );
            }
            else
            { // ランダムLv指定あり（最小値:get_level(),最大値:get_randomlevel()）
                randomlevel = _random.Next( template.get_randomlevel() - template.get_level() + 1 );
                diff = template.get_randomlevel() - template.get_level();
                rate = randomlevel / diff;
                randomlevel += template.get_level();
                setLevel( randomlevel );
            }
            if ( template.get_randomhp() == 0 )
            {
                setMaxHp( template.get_hp() );
                setCurrentHpDirect( template.get_hp() );
            }
            else
            {
                double randomhp = rate * ( template.get_randomhp() - template.get_hp() );
                int hp = (int)( template.get_hp() + randomhp );
                setMaxHp( hp );
                setCurrentHpDirect( hp );
            }
            if ( template.get_randommp() == 0 )
            {
                setMaxMp( template.get_mp() );
                setCurrentMpDirect( template.get_mp() );
            }
            else
            {
                double randommp = rate * ( template.get_randommp() - template.get_mp() );
                int mp = (int)( template.get_mp() + randommp );
                setMaxMp( mp );
                setCurrentMpDirect( mp );
            }
            if ( template.get_randomac() == 0 )
            {
                setAc( template.get_ac() );
            }
            else
            {
                double randomac = rate * ( template.get_randomac() - template.get_ac() );
                int ac = (int)( template.get_ac() + randomac );
                setAc( ac );
            }
            if ( template.get_randomlevel() == 0 )
            {
                setStr( template.get_str() );
                setCon( template.get_con() );
                setDex( template.get_dex() );
                setInt( template.get_int() );
                setWis( template.get_wis() );
                setMr( template.get_mr() );
            }
            else
            {
                setStr( (byte)Math.Min( template.get_str() + diff, 127 ) );
                setCon( (byte)Math.Min( template.get_con() + diff, 127 ) );
                setDex( (byte)Math.Min( template.get_dex() + diff, 127 ) );
                setInt( (byte)Math.Min( template.get_int() + diff, 127 ) );
                setWis( (byte)Math.Min( template.get_wis() + diff, 127 ) );
                setMr( (byte)Math.Min( template.get_mr() + diff, 127 ) );

                addHitup( (int)diff * 2 );
                addDmgup( (int)diff * 2 );
            }
            setPassispeed( template.get_passispeed() );
            setAtkspeed( template.get_atkspeed() );
            setAgro( template.is_agro() );
            setAgrocoi( template.is_agrocoi() );
            setAgrososc( template.is_agrososc() );
            setTempCharGfx( template.get_gfxid() );
            setGfxId( template.get_gfxid() );
            if ( template.get_randomexp() == 0 )
            {
                setExp( template.get_exp() );
            }
            else
            {
                int level = getLevel();
                int exp = level * level;
                exp += 1;
                setExp( exp );
            }
            if ( template.get_randomlawful() == 0 )
            {
                setLawful( template.get_lawful() );
                setTempLawful( template.get_lawful() );
            }
            else
            {
                double randomlawful = rate * ( template.get_randomlawful() - template.get_lawful() );
                int lawful = (int)( template.get_lawful() + randomlawful );
                setLawful( lawful );
                setTempLawful( lawful );
            }
            setPickupItem( template.is_picupitem() );
            if ( template.is_bravespeed() )
            {
                setBraveSpeed( 1 );
            }
            else
            {
                setBraveSpeed( 0 );
            }
            if ( template.get_digestitem() > 0 )
            {
                _digestItems = new Dictionary<int, int>();
            }
            setKarma( template.getKarma() );
            setLightSize( template.getLightSize() );

            mobSkill = new L1MobSkillUse( this );
        }

        private int _passispeed;

        public int getPassispeed()
        {
            return _passispeed;
        }

        public void setPassispeed( int i )
        {
            _passispeed = i;
        }

        private int _atkspeed;

        public int getAtkspeed()
        {
            return _atkspeed;
        }

        public void setAtkspeed( int i )
        {
            _atkspeed = i;
        }

        private bool _pickupItem;

        public bool isPickupItem()
        {
            return _pickupItem;
        }

        public void setPickupItem( bool flag )
        {
            _pickupItem = flag;
        }

        new public L1Inventory getInventory()
        {
            return _inventory;
        }

        public void setInventory( L1Inventory inventory )
        {
            _inventory = inventory;
        }

        public L1Npc getNpcTemplate()
        {
            return _npcTemplate;
        }

        public int getNpcId()
        {
            return _npcTemplate.get_npcId();
        }

        public void setPetcost( int i )
        {
            _petcost = i;
        }

        public int getPetcost()
        {
            return _petcost;
        }

        public void setSpawn( L1Spawn spawn )
        {
            _spawn = spawn;
        }

        public L1Spawn getSpawn()
        {
            return _spawn;
        }

        public void setSpawnNumber( int number )
        {
            _spawnNumber = number;
        }

        public int getSpawnNumber()
        {
            return _spawnNumber;
        }

        // オブジェクトIDをSpawnTaskに渡し再利用する
        // グループモンスターは複雑になるので再利用しない
        public void onDecay( bool isReuseId )
        {
            int id = 0;
            if ( isReuseId )
            {
                id = getId();
            }
            else
            {
                id = 0;
            }
            _spawn.executeSpawnTask( _spawnNumber, id );
        }

        public override void onPerceive( L1PcInstance perceivedFrom )
        {
            perceivedFrom.addKnownObject( this );
            perceivedFrom.sendPackets( new S_NPCPack( this ) );
            onNpcAI();
        }

        virtual
        public void deleteMe()
        {
            _destroyed = true;
            if ( getInventory() != null )
            {
                getInventory().clearItems();
            }
            allTargetClear();
            _master = null;
            L1World.getInstance().removeVisibleObject( this );
            L1World.getInstance().removeObject( this );
            List<L1PcInstance> players = L1World.getInstance().getRecognizePlayer( this );
            if ( players.Count > 0 )
            {
                S_RemoveObject s_deleteNewObject = new S_RemoveObject( this );
                foreach ( L1PcInstance pc in players )
                {
                    if ( pc != null )
                    {
                        pc.removeKnownObject( this );
                        // if(!L1Character.distancepc(user, this))
                        pc.sendPackets( s_deleteNewObject );
                    }
                }
            }
            removeAllKnownObjects();

            // リスパウン設定
            L1MobGroupInfo mobGroupInfo = getMobGroupInfo();
            if ( mobGroupInfo == null )
            {
                if ( isReSpawn() )
                {
                    onDecay( true );
                }
            }
            else
            {
                if ( mobGroupInfo.removeMember( this ) == 0 )
                { // グループメンバー全滅
                    setMobGroupInfo( null );
                    if ( isReSpawn() )
                    {
                        onDecay( false );
                    }
                }
            }
        }

        virtual public void ReceiveManaDamage( L1Character attacker, int damageMp ) { }


        virtual public void receiveDamage( L1Character attacker, int damage ) { }

        public void setDigestItem( L1ItemInstance item )
        {
            _digestItems[item.getId()] = getNpcTemplate().get_digestitem();
            if ( !_digestItemRunning )
            {
                // TODO 優先度9 スレッドプール
                //DigestItemTimer digestItemTimer = new DigestItemTimer();
                //GeneralThreadPool.getInstance( ).execute( digestItemTimer );
                ThreadPool.QueueUserWorkItem( new WaitCallback( DigestItemTimerSub ) );
            }
        }

        virtual public void onGetItem( L1ItemInstance item )
        {
            refineItem();
            getInventory().shuffle();
            if ( getNpcTemplate().get_digestitem() > 0 )
            {
                setDigestItem( item );
            }
        }

        public void approachPlayer( L1PcInstance pc )
        {
            if ( pc.hasSkillEffect( 60 ) || pc.hasSkillEffect( 97 ) )
            { // インビジビリティ、ブラインドハイディング中
                return;
            }
            if ( getHiddenStatus() == HIDDEN_STATUS_SINK )
            {
                if ( getCurrentHp() == getMaxHp() )
                {
                    if ( pc.getLocation().getTileLineDistance( this.getLocation() ) <= 2 )
                    {
                        appearOnGround( pc );
                    }
                }
            }
            else if ( getHiddenStatus() == HIDDEN_STATUS_FLY )
            {
                if ( getCurrentHp() == getMaxHp() )
                {
                    if ( pc.getLocation().getTileLineDistance( this.getLocation() ) <= 1 )
                    {
                        appearOnGround( pc );
                    }
                }
                else
                {
                    // if (getNpcTemplate().get_npcId() != 45681) { // リンドビオル以外
                    searchItemFromAir();
                    // }
                }
            }
            else if ( getHiddenStatus() == HIDDEN_STATUS_ICE )
            {
                if ( getCurrentHp() < getMaxHp() )
                {
                    appearOnGround( pc );
                }
            }
        }

        public void appearOnGround( L1PcInstance pc )
        {
            if ( getHiddenStatus() == HIDDEN_STATUS_SINK
                    || getHiddenStatus() == HIDDEN_STATUS_ICE )
            {
                setHiddenStatus( HIDDEN_STATUS_NONE );
                broadcastPacket( new S_DoActionGFX( getId(),
                        ActionCodes.ACTION_Appear ) );
                setStatus( 0 );
                broadcastPacket( new S_NPCPack( this ) );
                if ( !pc.hasSkillEffect( 60 ) && !pc.hasSkillEffect( 97 ) // インビジビリティ、ブラインドハイディング中以外、GM以外
                        && !pc.isGm() )
                {
                    _hateList.add( pc, 0 );
                    _target = pc;
                }
                onNpcAI(); // モンスターのＡＩを開始
            }
            else if ( getHiddenStatus() == HIDDEN_STATUS_FLY )
            {
                setHiddenStatus( HIDDEN_STATUS_NONE );
                broadcastPacket( new S_DoActionGFX( getId(),
                        ActionCodes.ACTION_Movedown ) );
                setStatus( 0 );
                broadcastPacket( new S_NPCPack( this ) );
                if ( !pc.hasSkillEffect( 60 ) && !pc.hasSkillEffect( 97 ) // インビジビリティ、ブラインドハイディング中以外、GM以外
                        && !pc.isGm() )
                {
                    _hateList.add( pc, 0 );
                    _target = pc;
                }
                onNpcAI(); // モンスターのＡＩを開始
                startChat( CHAT_TIMING_HIDE );
            }
        }

        // ■■■■■■■■■■■■■ 移動関連 ■■■■■■■■■■■

        // 指定された方向に移動させる
        public void setDirectionMove( int dir )
        {
            if ( dir >= 0 )
            {
                int nx = 0;
                int ny = 0;

                switch ( dir )
                {
                    case 1:
                        nx = 1;
                        ny = -1;
                        setHeading( 1 );
                        break;

                    case 2:
                        nx = 1;
                        ny = 0;
                        setHeading( 2 );
                        break;

                    case 3:
                        nx = 1;
                        ny = 1;
                        setHeading( 3 );
                        break;

                    case 4:
                        nx = 0;
                        ny = 1;
                        setHeading( 4 );
                        break;

                    case 5:
                        nx = -1;
                        ny = 1;
                        setHeading( 5 );
                        break;

                    case 6:
                        nx = -1;
                        ny = 0;
                        setHeading( 6 );
                        break;

                    case 7:
                        nx = -1;
                        ny = -1;
                        setHeading( 7 );
                        break;

                    case 0:
                        nx = 0;
                        ny = -1;
                        setHeading( 0 );
                        break;

                    default:
                        break;

                }

                getMap().setPassable( getLocation(), true );

                int nnx = getX() + nx;
                int nny = getY() + ny;
                setX( nnx );
                setY( nny );

                getMap().setPassable( getLocation(), false );

                broadcastPacket( new S_MoveCharPacket( this ) );

                // movement_distanceマス以上離れたらホームポイントへテレポート
                if ( getMovementDistance() > 0 )
                {
                    if ( this is L1GuardInstance
                            || this is L1MerchantInstance
                            || this is L1MonsterInstance )
                    {
                        if ( getLocation().getLineDistance(
                                new Point( getHomeX(), getHomeY() ) ) > getMovementDistance() )
                        {
                            teleport( getHomeX(), getHomeY(), getHeading() );
                        }
                    }
                }
                // 恨みに満ちたソルジャーゴースト、恨みに満ちたゴースト、恨みに満ちたハメル将軍
                if ( getNpcTemplate().get_npcId() >= 45912
                        && getNpcTemplate().get_npcId() <= 45916 )
                {
                    if ( getX() >= 32591 && getX() <= 32644
                            && getY() >= 32643 && getY() <= 32688
                                    && getMapId() == 4 )
                    {
                        teleport( getHomeX(), getHomeY(), getHeading() );
                    }
                }
            }
        }

        public int moveDirection( int x, int y )
        { // 目標点Ｘ 目標点Ｙ
            return moveDirection( x, y, getLocation().getLineDistance(
                    new Point( x, y ) ) );
        }

        // 目標までの距離に応じて最適と思われるルーチンで進む方向を返す
        public int moveDirection( int x, int y, double d )
        { // 目標点Ｘ 目標点Ｙ 目標までの距離
            int dir = 0;
            if ( hasSkillEffect( 40 ) == true && d >= 2D )
            { // ダークネスが掛かっていて、距離が2以上の場合追跡終了
                return -1;
            }
            else if ( d > 30D )
            { // 距離が激しく遠い場合は追跡終了
                return -1;
            }
            else if ( d > courceRange )
            { // 距離が遠い場合は単純計算
                dir = targetDirection( x, y );
                dir = checkObject( getX(), getY(), getMapId(), dir );
            }
            else
            { // 目標までの最短経路を探索
                dir = _serchCource( x, y );
                if ( dir == -1 )
                { // 目標までの経路がなっかた場合はとりあえず近づいておく
                    dir = targetDirection( x, y );
                    if ( !isExsistCharacterBetweenTarget( dir ) )
                    {
                        dir = checkObject( getX(), getY(), getMapId(), dir );
                    }
                }
            }
            return dir;
        }

        private bool isExsistCharacterBetweenTarget( int dir )
        {
            if ( !( this is L1MonsterInstance ) )
            { // モンスター以外は対象外
                return false;
            }
            if ( _target == null )
            { // ターゲットがいない場合
                return false;
            }

            int locX = getX();
            int locY = getY();
            int targetX = locX;
            int targetY = locY;

            if ( dir == 1 )
            {
                targetX = locX + 1;
                targetY = locY - 1;
            }
            else if ( dir == 2 )
            {
                targetX = locX + 1;
            }
            else if ( dir == 3 )
            {
                targetX = locX + 1;
                targetY = locY + 1;
            }
            else if ( dir == 4 )
            {
                targetY = locY + 1;
            }
            else if ( dir == 5 )
            {
                targetX = locX - 1;
                targetY = locY + 1;
            }
            else if ( dir == 6 )
            {
                targetX = locX - 1;
            }
            else if ( dir == 7 )
            {
                targetX = locX - 1;
                targetY = locY - 1;
            }
            else if ( dir == 0 )
            {
                targetY = locY - 1;
            }

            foreach ( L1Object l1object in L1World.getInstance().getVisibleObjects( this, 1 ) )
            {
                // PC, Summon, Petがいる場合
                if ( l1object is L1PcInstance
                        || l1object is L1SummonInstance
                        || l1object is L1PetInstance )
                {
                    L1Character cha = (L1Character)l1object;
                    // 進行方向に立ちふさがっている場合、ターゲットリストに加える
                    if ( cha.getX() == targetX && cha.getY() == targetY
                            && cha.getMapId() == getMapId() )
                    {
                        if ( l1object is L1PcInstance )
                        {
                            L1PcInstance pc = (L1PcInstance)l1object;
                            if ( pc.isGhost() )
                            { // UB観戦中のPCは除く
                                continue;
                            }
                        }
                        _hateList.add( cha, 0 );
                        _target = cha;
                        return true;
                    }
                }
            }
            return false;
        }

        // 目標の逆方向を返す
        public int targetReverseDirection( int tx, int ty )
        { // 目標点Ｘ 目標点Ｙ
            int dir = targetDirection( tx, ty );
            dir += 4;
            if ( dir > 7 )
            {
                dir -= 8;
            }
            return dir;
        }

        // 進みたい方向に障害物がないか確認、ある場合は前方斜め左右も確認後進める方向を返す
        // ※従来あった処理に、バックできない仕様を省いて、目標の反対（左右含む）には進まないようにしたもの
        public static int checkObject( int x, int y, short m, int d )
        { // 起点Ｘ 起点Ｙ
            // マップＩＤ
            // 進行方向
            L1Map map = L1WorldMap.getInstance().getMap( m );
            if ( d == 1 )
            {
                if ( map.isPassable( x, y, 1 ) )
                {
                    return 1;
                }
                else if ( map.isPassable( x, y, 0 ) )
                {
                    return 0;
                }
                else if ( map.isPassable( x, y, 2 ) )
                {
                    return 2;
                }
            }
            else if ( d == 2 )
            {
                if ( map.isPassable( x, y, 2 ) )
                {
                    return 2;
                }
                else if ( map.isPassable( x, y, 1 ) )
                {
                    return 1;
                }
                else if ( map.isPassable( x, y, 3 ) )
                {
                    return 3;
                }
            }
            else if ( d == 3 )
            {
                if ( map.isPassable( x, y, 3 ) )
                {
                    return 3;
                }
                else if ( map.isPassable( x, y, 2 ) )
                {
                    return 2;
                }
                else if ( map.isPassable( x, y, 4 ) )
                {
                    return 4;
                }
            }
            else if ( d == 4 )
            {
                if ( map.isPassable( x, y, 4 ) )
                {
                    return 4;
                }
                else if ( map.isPassable( x, y, 3 ) )
                {
                    return 3;
                }
                else if ( map.isPassable( x, y, 5 ) )
                {
                    return 5;
                }
            }
            else if ( d == 5 )
            {
                if ( map.isPassable( x, y, 5 ) )
                {
                    return 5;
                }
                else if ( map.isPassable( x, y, 4 ) )
                {
                    return 4;
                }
                else if ( map.isPassable( x, y, 6 ) )
                {
                    return 6;
                }
            }
            else if ( d == 6 )
            {
                if ( map.isPassable( x, y, 6 ) )
                {
                    return 6;
                }
                else if ( map.isPassable( x, y, 5 ) )
                {
                    return 5;
                }
                else if ( map.isPassable( x, y, 7 ) )
                {
                    return 7;
                }
            }
            else if ( d == 7 )
            {
                if ( map.isPassable( x, y, 7 ) )
                {
                    return 7;
                }
                else if ( map.isPassable( x, y, 6 ) )
                {
                    return 6;
                }
                else if ( map.isPassable( x, y, 0 ) )
                {
                    return 0;
                }
            }
            else if ( d == 0 )
            {
                if ( map.isPassable( x, y, 0 ) )
                {
                    return 0;
                }
                else if ( map.isPassable( x, y, 7 ) )
                {
                    return 7;
                }
                else if ( map.isPassable( x, y, 1 ) )
                {
                    return 1;
                }
            }
            return -1;
        }

        // 目標までの最短経路の方向を返す
        // ※目標を中心とした探索範囲のマップで探索
        private int _serchCource( int x, int y ) // 目標点Ｘ 目標点Ｙ
        {
            int i;
            int locCenter = courceRange + 1;
            int diff_x = x - locCenter; // Ｘの実際のロケーションとの差
            int diff_y = y - locCenter; // Ｙの実際のロケーションとの差
            int[] locBace = { getX() - diff_x, getY() - diff_y, 0, 0 }; // Ｘ Ｙ
            // 方向
            // 初期方向
            int[] locNext = new int[4];
            int[] locCopy;
            int[] dirFront = new int[5];
            bool[,] serchMap = new bool[locCenter * 2 + 1, locCenter * 2 + 1];
            LinkedList<int[]> queueSerch = new LinkedList<int[]>();

            // 探索用マップの設定
            for ( int j = courceRange * 2 + 1; j > 0; j-- )
            {
                for ( i = courceRange - Math.Abs( locCenter - j ); i >= 0; i-- )
                {
                    serchMap[j, locCenter + i] = true;
                    serchMap[j, locCenter - i] = true;
                }
            }

            // 初期方向の設置  
            int[] firstCource = { 2, 4, 6, 0, 1, 3, 5, 7 };
            for ( i = 0; i < 8; i++ )
            {
                Array.Copy( locBace, 0, locNext, 0, 4 );
                _moveLocation( locNext, firstCource[i] );
                if ( locNext[0] - locCenter == 0 && locNext[1] - locCenter == 0 )
                {
                    //最短経路が見つかった場合:隣
                    return firstCource[i];
                }
                if ( serchMap[locNext[0], locNext[1]] )
                {
                    int tmpX = locNext[0] + diff_x;
                    int tmpY = locNext[1] + diff_y;
                    bool found = false;
                    if ( i == 0 )
                    {
                        found = getMap().isPassable( tmpX, tmpY + 1, i );
                    }
                    else if ( i == 1 )
                    {
                        found = getMap().isPassable( tmpX - 1, tmpY + 1, i );
                    }
                    else if ( i == 2 )
                    {
                        found = getMap().isPassable( tmpX - 1, tmpY, i );
                    }
                    else if ( i == 3 )
                    {
                        found = getMap().isPassable( tmpX - 1, tmpY - 1, i );
                    }
                    else if ( i == 4 )
                    {
                        found = getMap().isPassable( tmpX, tmpY - 1, i );
                    }
                    else if ( i == 5 )
                    {
                        found = getMap().isPassable( tmpX + 1, tmpY - 1, i );
                    }
                    else if ( i == 6 )
                    {
                        found = getMap().isPassable( tmpX + 1, tmpY, i );
                    }
                    else if ( i == 7 )
                    {
                        found = getMap().isPassable( tmpX + 1, tmpY + 1, i );
                    }
                    if ( found )// 移動経路があった場合
                    {
                        locCopy = new int[4];
                        Array.Copy( locNext, 0, locCopy, 0, 4 );
                        locCopy[2] = firstCource[i];
                        locCopy[3] = firstCource[i];
                        queueSerch.AddLast( locCopy );
                    }
                    serchMap[locNext[0], locNext[1]] = false;
                }
            }
            locBace = null;

            // 最短経路を探索
            while ( queueSerch.Count > 0 )
            {
                locBace = queueSerch.First.Value;
                queueSerch.RemoveFirst();
                _getFront( dirFront, locBace[2] );
                for ( i = 4; i >= 0; i-- )
                {
                    Array.Copy( locBace, 0, locNext, 0, 4 );
                    _moveLocation( locNext, dirFront[i] );
                    if ( locNext[0] - locCenter == 0 && locNext[1] - locCenter == 0 )
                    {
                        return locNext[3];
                    }
                    if ( serchMap[locNext[0], locNext[1]] )
                    {
                        int tmpX = locNext[0] + diff_x;
                        int tmpY = locNext[1] + diff_y;
                        bool found = false;
                        if ( i == 0 )
                        {
                            found = getMap().isPassable( tmpX, tmpY + 1, i );
                        }
                        else if ( i == 1 )
                        {
                            found = getMap().isPassable( tmpX - 1, tmpY + 1, i );
                        }
                        else if ( i == 2 )
                        {
                            found = getMap().isPassable( tmpX - 1, tmpY, i );
                        }
                        else if ( i == 3 )
                        {
                            found = getMap().isPassable( tmpX - 1, tmpY - 1, i );
                        }
                        else if ( i == 4 )
                        {
                            found = getMap().isPassable( tmpX, tmpY - 1, i );
                        }
                        if ( found ) // 移動経路があった場合
                        {
                            locCopy = new int[4];
                            Array.Copy( locNext, 0, locCopy, 0, 4 );
                            locCopy[2] = dirFront[i];
                            queueSerch.AddLast( locCopy );
                        }
                        serchMap[locNext[0], locNext[1]] = false;
                    }
                }
                locBace = null;
            }
            return -1; // 目標までの経路がない場合
        }

        private void _moveLocation( int[] ary, int d )
        {
            if ( d == 1 )
            {
                ary[0] = ary[0] + 1;
                ary[1] = ary[1] - 1;
            }
            else if ( d == 2 )
            {
                ary[0] = ary[0] + 1;
            }
            else if ( d == 3 )
            {
                ary[0] = ary[0] + 1;
                ary[1] = ary[1] + 1;
            }
            else if ( d == 4 )
            {
                ary[1] = ary[1] + 1;
            }
            else if ( d == 5 )
            {
                ary[0] = ary[0] - 1;
                ary[1] = ary[1] + 1;
            }
            else if ( d == 6 )
            {
                ary[0] = ary[0] - 1;
            }
            else if ( d == 7 )
            {
                ary[0] = ary[0] - 1;
                ary[1] = ary[1] - 1;
            }
            else if ( d == 0 )
            {
                ary[1] = ary[1] - 1;
            }
            ary[2] = d;
        }

        private void _getFront( int[] ary, int d )
        {
            if ( d == 1 )
            {
                ary[4] = 2;
                ary[3] = 0;
                ary[2] = 1;
                ary[1] = 3;
                ary[0] = 7;
            }
            else if ( d == 2 )
            {
                ary[4] = 2;
                ary[3] = 4;
                ary[2] = 0;
                ary[1] = 1;
                ary[0] = 3;
            }
            else if ( d == 3 )
            {
                ary[4] = 2;
                ary[3] = 4;
                ary[2] = 1;
                ary[1] = 3;
                ary[0] = 5;
            }
            else if ( d == 4 )
            {
                ary[4] = 2;
                ary[3] = 4;
                ary[2] = 6;
                ary[1] = 3;
                ary[0] = 5;
            }
            else if ( d == 5 )
            {
                ary[4] = 4;
                ary[3] = 6;
                ary[2] = 3;
                ary[1] = 5;
                ary[0] = 7;
            }
            else if ( d == 6 )
            {
                ary[4] = 4;
                ary[3] = 6;
                ary[2] = 0;
                ary[1] = 5;
                ary[0] = 7;
            }
            else if ( d == 7 )
            {
                ary[4] = 6;
                ary[3] = 0;
                ary[2] = 1;
                ary[1] = 5;
                ary[0] = 7;
            }
            else if ( d == 0 )
            {
                ary[4] = 2;
                ary[3] = 6;
                ary[2] = 0;
                ary[1] = 1;
                ary[0] = 7;
            }
        }

        // ■■■■■■■■■■■■ アイテム関連 ■■■■■■■■■■

        private void useHealPotion( int healHp, int effectId )
        {
            broadcastPacket( new S_SkillSound( getId(), effectId ) );
            if ( this.hasSkillEffect( L1SkillId.POLLUTE_WATER ) )
            { // ポルートウォーター中は回復量1/2倍
                healHp /= 2;
            }
            if ( this is L1PetInstance )
            {
                ( (L1PetInstance)this ).setCurrentHp( getCurrentHp() + healHp );
            }
            else if ( this is L1SummonInstance )
            {
                ( (L1SummonInstance)this ).setCurrentHp( getCurrentHp() + healHp );
            }
            else
            {
                setCurrentHpDirect( getCurrentHp() + healHp );
            }
        }

        private void useHastePotion( int time )
        {
            broadcastPacket( new S_SkillHaste( getId(), 1, time ) );
            broadcastPacket( new S_SkillSound( getId(), 191 ) );
            setMoveSpeed( 1 );
            setSkillEffect( L1SkillId.STATUS_HASTE, time * 1000 );
        }

        // アイテムの使用判定及び使用
        public const int USEITEM_HEAL = 0;
        public const int USEITEM_HASTE = 1;
        public static int[] healPotions = { L1ItemId.POTION_OF_GREATER_HEALING,
                    L1ItemId.POTION_OF_EXTRA_HEALING, L1ItemId.POTION_OF_HEALING };
        public static int[] haestPotions = {L1ItemId. B_POTION_OF_GREATER_HASTE_SELF,
                    L1ItemId.POTION_OF_GREATER_HASTE_SELF,L1ItemId. B_POTION_OF_HASTE_SELF,
                    L1ItemId.POTION_OF_HASTE_SELF };

        public void useItem( int type, int chance )
        { // 使用する種類 使用する可能性(％)
            if ( hasSkillEffect( 71 ) )
            {
                return; // ディケイ ポーション状態かチェック
            }

            Random random = new Random();
            if ( random.Next( 100 ) > chance )
            {
                return; // 使用する可能性
            }

            if ( type == USEITEM_HEAL )
            { // 回復系ポーション
                // 回復量の大きい順
                if ( getInventory().consumeItem( L1ItemId.POTION_OF_GREATER_HEALING, 1 ) )
                {
                    useHealPotion( 75, 197 );
                }
                else if ( getInventory().consumeItem( L1ItemId.POTION_OF_EXTRA_HEALING, 1 ) )
                {
                    useHealPotion( 45, 194 );
                }
                else if ( getInventory().consumeItem( L1ItemId.POTION_OF_HEALING, 1 ) )
                {
                    useHealPotion( 15, 189 );
                }
            }
            else if ( type == USEITEM_HASTE )
            { // ヘイスト系ポーション
                if ( hasSkillEffect( 1001 ) )
                {
                    return; // ヘイスト状態チェック
                }

                // 効果の長い順
                if ( getInventory().consumeItem( L1ItemId.B_POTION_OF_GREATER_HASTE_SELF, 1 ) )
                {
                    useHastePotion( 2100 );
                }
                else if ( getInventory().consumeItem( L1ItemId.POTION_OF_GREATER_HASTE_SELF,
                      1 ) )
                {
                    useHastePotion( 1800 );
                }
                else if ( getInventory().consumeItem( L1ItemId.B_POTION_OF_HASTE_SELF, 1 ) )
                {
                    useHastePotion( 350 );
                }
                else if ( getInventory().consumeItem( L1ItemId.POTION_OF_HASTE_SELF, 1 ) )
                {
                    useHastePotion( 300 );
                }
            }
        }

        // ■■■■■■■■■■■■■ スキル関連(npcskillsテーブル実装されたら消すかも) ■■■■■■■■■■■

        // 目標の隣へテレポート
        public bool nearTeleport( int nx, int ny )
        {
            int rdir = _random.Next( 8 );
            int dir;
            for ( int i = 0; i < 8; i++ )
            {
                dir = rdir + i;
                if ( dir > 7 )
                {
                    dir -= 8;
                }
                if ( dir == 1 )
                {
                    nx++;
                    ny--;
                }
                else if ( dir == 2 )
                {
                    nx++;
                }
                else if ( dir == 3 )
                {
                    nx++;
                    ny++;
                }
                else if ( dir == 4 )
                {
                    ny++;
                }
                else if ( dir == 5 )
                {
                    nx--;
                    ny++;
                }
                else if ( dir == 6 )
                {
                    nx--;
                }
                else if ( dir == 7 )
                {
                    nx--;
                    ny--;
                }
                else if ( dir == 0 )
                {
                    ny--;
                }
                if ( getMap().isPassable( nx, ny ) )
                {
                    dir += 4;
                    if ( dir > 7 )
                    {
                        dir -= 8;
                    }
                    teleport( nx, ny, dir );
                    setCurrentMp( getCurrentMp() - 10 );
                    return true;
                }
            }
            return false;
        }

        // 目標へテレポート
        public void teleport( int nx, int ny, int dir )
        {
            foreach ( L1PcInstance pc in L1World.getInstance().getRecognizePlayer( this ) )
            {
                pc.sendPackets( new S_SkillSound( getId(), 169 ) );
                pc.sendPackets( new S_RemoveObject( this ) );
                pc.removeKnownObject( this );
            }
            setX( nx );
            setY( ny );
            setHeading( dir );
        }

        // ----------From L1Character-------------
        private String _nameId; // ● ネームＩＤ

        public String getNameId()
        {
            return _nameId;
        }

        public void setNameId( String s )
        {
            _nameId = s;
        }

        private bool _Agro; // ● アクティブか

        public bool isAgro()
        {
            return _Agro;
        }

        public void setAgro( bool flag )
        {
            _Agro = flag;
        }

        private bool _Agrocoi; // ● インビジアクティブか

        public bool isAgrocoi()
        {
            return _Agrocoi;
        }

        public void setAgrocoi( bool flag )
        {
            _Agrocoi = flag;
        }

        private bool _Agrososc; // ● 変身アクティブか

        public bool isAgrososc()
        {
            return _Agrososc;
        }

        public void setAgrososc( bool flag )
        {
            _Agrososc = flag;
        }

        private int _homeX; // ● ホームポイントＸ（モンスターの戻る位置とかペットの警戒位置）

        public int getHomeX()
        {
            return _homeX;
        }

        public void setHomeX( int i )
        {
            _homeX = i;
        }

        private int _homeY; // ● ホームポイントＹ（モンスターの戻る位置とかペットの警戒位置）

        public int getHomeY()
        {
            return _homeY;
        }

        public void setHomeY( int i )
        {
            _homeY = i;
        }

        private bool _reSpawn; // ● 再ポップするかどうか

        public bool isReSpawn()
        {
            return _reSpawn;
        }

        public void setreSpawn( bool flag )
        {
            _reSpawn = flag;
        }

        private int _lightSize; // ● ライト ０．なし １～１４．大きさ

        public int getLightSize()
        {
            return _lightSize;
        }

        public void setLightSize( int i )
        {
            _lightSize = i;
        }

        private bool _weaponBreaked; // ● ウェポンブレイク中かどうか

        public bool isWeaponBreaked()
        {
            return _weaponBreaked;
        }

        public void setWeaponBreaked( bool flag )
        {
            _weaponBreaked = flag;
        }

        private int _hiddenStatus; // ● 地中に潜ったり、空を飛んでいる状態

        public int getHiddenStatus()
        {
            return _hiddenStatus;
        }

        public void setHiddenStatus( int i )
        {
            _hiddenStatus = i;
        }

        // 行動距離
        private int _movementDistance = 0;

        public int getMovementDistance()
        {
            return _movementDistance;
        }

        public void setMovementDistance( int i )
        {
            _movementDistance = i;
        }

        // 表示用ロウフル
        private int _tempLawful = 0;

        public int getTempLawful()
        {
            return _tempLawful;
        }

        public void setTempLawful( int i )
        {
            _tempLawful = i;
        }

        protected int calcSleepTime( int sleepTime, int type )
        {
            switch ( getMoveSpeed() )
            {
                case 0: // 通常
                    break;
                case 1: // ヘイスト
                    sleepTime -= (int)( sleepTime * 0.25 );
                    break;
                case 2: // スロー
                    sleepTime *= 2;
                    break;
            }
            if ( getBraveSpeed() == 1 )
            {
                sleepTime -= (int)( sleepTime * 0.25 );
            }
            if ( hasSkillEffect( L1SkillId.WIND_SHACKLE ) )
            {
                if ( type == ATTACK_SPEED || type == MAGIC_SPEED )
                {
                    sleepTime += (int)( sleepTime * 0.25 );
                }
            }
            return sleepTime;
        }

        protected void setAiRunning( bool aiRunning )
        {
            _aiRunning = aiRunning;
        }

        protected bool isAiRunning()
        {
            return _aiRunning;
        }

        protected void setActived( bool actived )
        {
            _actived = actived;
        }

        protected bool isActived()
        {
            return _actived;
        }

        protected void setFirstAttack( bool firstAttack )
        {
            _firstAttack = firstAttack;
        }

        protected bool isFirstAttack()
        {
            return _firstAttack;
        }

        protected void setSleepTime( int sleep_time )
        {
            _sleep_time = sleep_time;
        }

        protected int getSleepTime()
        {
            return _sleep_time;
        }

        protected void setDeathProcessing( bool deathProcessing )
        {
            _deathProcessing = deathProcessing;
        }

        protected bool isDeathProcessing()
        {
            return _deathProcessing;
        }

        public int drainMana( int drain )
        {
            if ( _drainedMana >= Config.MANA_DRAIN_LIMIT_PER_NPC )
            {
                return 0;
            }
            int result = Math.Min( drain, getCurrentMp() );
            if ( _drainedMana + result > Config.MANA_DRAIN_LIMIT_PER_NPC )
            {
                result = Config.MANA_DRAIN_LIMIT_PER_NPC - _drainedMana;
            }
            _drainedMana += result;
            return result;
        }

        public bool _destroyed = false; // このインスタンスが破棄されているか

        // ※破棄後に動かないよう強制的にＡＩ等のスレッド処理中止（念のため）

        // NPCが別のNPCに変わる場合の処理
        virtual protected void transform( int transformId )
        {
            stopHpRegeneration();
            stopMpRegeneration();
            int transformGfxId = getNpcTemplate().getTransformGfxId();
            if ( transformGfxId != 0 )
            {
                broadcastPacket( new S_SkillSound( getId(), transformGfxId ) );
            }
            L1Npc npcTemplate = NpcTable.getInstance().getTemplate( transformId );
            setting_template( npcTemplate );

            broadcastPacket( new S_ChangeShape( getId(), getTempCharGfx() ) );
            foreach ( L1PcInstance pc in L1World.getInstance().getRecognizePlayer( this ) )
            {
                onPerceive( pc );
            }

        }

        public void setRest( bool _rest )
        {
            this._rest = _rest;
        }

        public bool isRest()
        {
            return _rest;
        }

        private bool _isResurrect;

        public bool isResurrect()
        {
            return _isResurrect;
        }

        public void setResurrect( bool flag )
        {
            _isResurrect = flag;
        }

        override
        public void resurrect( int hp )
        {
            lock ( this )
            {
                if ( _destroyed )
                {
                    return;
                }
                if ( _future != null )
                {
                    _future.Dispose();
                    // TODO 優先度6 タイマースレッドの停止ができない場合の対処？
                    //if (!_future.cancel(false))
                    //{ // キャンセルできない
                    //    return;
                    //}
                    _future = null;
                    // _future = null;
                }
                base.resurrect( hp );

                // キャンセレーションをエフェクトなしでかける
                // 本来は死亡時に行うべきだが、負荷が大きくなるため復活時に行う
                L1SkillUse skill = new L1SkillUse();
                skill.handleCommands( null, L1SkillId.CANCELLATION, getId(), getX(),
                        getY(), null, 0, L1SkillUse.TYPE_LOGIN, this );
            }
        }

        // 死んでから消えるまでの時間計測用
        private Timer _future = null;

        protected void startDeleteTimer()
        {
            lock ( this )
            {
                if ( _future != null )
                {
                    return;
                }
                //_deleteTask = new DeleteTimer( getId() );
                //_future = GeneralThreadPool.getInstance().schedule( _deleteTask, Config.NPC_DELETION_TIME * 1000 );
                _future = new Timer( new TimerCallback( DeleteTimerSub ), null, Config.NPC_DELETION_TIME * 1000, Timeout.Infinite );
            }
        }

        //class DeleteTimer : L1NpcInstance
        //{
        //private int _id;

        //public DeleteTimer( int oId )
        //{
        //    _id = oId;
        //    if ( !( L1World.getInstance().findObject( _id ) is L1NpcInstance ) )
        //    {
        //        throw new ArithmeticException( "allowed only L1NpcInstance" );
        //    }
        //}

        public void DeleteTimerSub( object state )
        {
            L1NpcInstance npc = (L1NpcInstance)L1World.getInstance().findObject( getId() );
            if ( npc == null || !npc.isDead() || npc._destroyed )
            {
                return; // 復活してるか、既に破棄済みだったら抜け
            }
            npc.deleteMe();
        }
        //}

        private L1MobGroupInfo _mobGroupInfo = null;

        public bool isInMobGroup()
        {
            return getMobGroupInfo() != null;
        }

        public L1MobGroupInfo getMobGroupInfo()
        {
            return _mobGroupInfo;
        }

        public void setMobGroupInfo( L1MobGroupInfo m )
        {
            _mobGroupInfo = m;
        }

        private int _mobGroupId = 0;

        public int getMobGroupId()
        {
            return _mobGroupId;
        }

        public void setMobGroupId( int i )
        {
            _mobGroupId = i;
        }

        public void startChat( int chatTiming )
        {
            // 出現時のチャットにも関わらず死亡中、死亡時のチャットにも関わらず生存中
            if ( chatTiming == CHAT_TIMING_APPEARANCE && this.isDead() )
            {
                return;
            }
            if ( chatTiming == CHAT_TIMING_DEAD && !this.isDead() )
            {
                return;
            }
            if ( chatTiming == CHAT_TIMING_HIDE && this.isDead() )
            {
                return;
            }
            if ( chatTiming == CHAT_TIMING_GAME_TIME && this.isDead() )
            {
                return;
            }

            int npcId = this.getNpcTemplate().get_npcId();
            L1NpcChat npcChat = null;
            if ( chatTiming == CHAT_TIMING_APPEARANCE )
            {
                npcChat = NpcChatTable.getInstance().getTemplateAppearance( npcId );
            }
            else if ( chatTiming == CHAT_TIMING_DEAD )
            {
                npcChat = NpcChatTable.getInstance().getTemplateDead( npcId );
            }
            else if ( chatTiming == CHAT_TIMING_HIDE )
            {
                npcChat = NpcChatTable.getInstance().getTemplateHide( npcId );
            }
            else if ( chatTiming == CHAT_TIMING_GAME_TIME )
            {
                npcChat = NpcChatTable.getInstance().getTemplateGameTime( npcId );
            }
            if ( npcChat == null )
            {
                return;
            }

            //Timer timer = new Timer( true );
            L1NpcChatTimer npcChatTimer = new L1NpcChatTimer( this, npcChat );
            if ( !npcChat.isRepeat() )
            {
                // TODO 優先度9 タイマースレッド
                //timer.schedule( npcChatTimer, npcChat.getStartDelayTime() );
                new Timer( new TimerCallback( npcChatTimer.run ), null, npcChat.getStartDelayTime(), Timeout.Infinite );
            }
            else
            {
                // TODO 優先度9 タイマースレッド
                //timer.scheduleAtFixedRate( npcChatTimer, npcChat.getStartDelayTime(), npcChat.getRepeatInterval() );
                new Timer( new TimerCallback( npcChatTimer.run ), null, npcChat.getStartDelayTime(), npcChat.getRepeatInterval() );
            }
        }
    }
}
